/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.builder;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.jianggujin.dbfly.mybatis.JConfiguration;
import com.jianggujin.dbfly.mybatis.builder.JDefaultMappedStatementBuilder.JParser;
import com.jianggujin.dbfly.mybatis.builder.mapper.JMethodMapper;
import com.jianggujin.dbfly.mybatis.builder.method.JCriteria;
import com.jianggujin.dbfly.mybatis.builder.method.JCriterion;
import com.jianggujin.dbfly.mybatis.builder.method.JOrder;
import com.jianggujin.dbfly.mybatis.builder.method.JOrderBy;
import com.jianggujin.dbfly.mybatis.builder.method.JOrderProperty;
import com.jianggujin.dbfly.mybatis.builder.method.JPart;
import com.jianggujin.dbfly.mybatis.builder.method.JPartTree;
import com.jianggujin.dbfly.mybatis.builder.method.JPartTree.JOrPart;
import com.jianggujin.dbfly.mybatis.builder.method.JProperty;
import com.jianggujin.dbfly.mybatis.builder.method.JSort;
import com.jianggujin.dbfly.mybatis.builder.method.JWhere;
import com.jianggujin.dbfly.mybatis.entity.JEntity;
import com.jianggujin.dbfly.mybatis.entity.JEntityColumn;
import com.jianggujin.dbfly.mybatis.util.JElementBuilder;
import com.jianggujin.dbfly.mybatis.util.JProviderHelper;
import com.jianggujin.dbfly.util.JDBFlyException;
import com.jianggujin.dbfly.util.JIntRef;
import com.jianggujin.dbfly.util.JObjectRef;
import com.jianggujin.dbfly.util.JStringUtils;

/**
 * 方法名解析
 * 
 * @author jianggujin
 *
 */
public class JMethodNameParser implements JParser {
    @Override
    public boolean support(JConfiguration configuration, Class<?> mapperClass, Method method, JObjectRef<Object> ref) {
        JPartTree partTree = null;
        if (JMethodMapper.class.isAssignableFrom(mapperClass)
                && (partTree = new JPartTree(method.getName())).isValid()) {
            ref.setValue(partTree);
            return true;
        }
        return false;
    }

    @Override
    public void parse(JConfiguration configuration, Class<?> mapperClass, Method method, Document document,
            Element mapperElement, Object ref) {
        JPartTree partTree = new JPartTree(method.getName());
        if (partTree.isValid()) {
            this.build(partTree, configuration, mapperClass, method, document, mapperElement);
        }
    }

    /**
     * 通过方法名构建XML
     * 
     * @param partTree
     * @param configuration
     * @param mapperClass
     * @param method
     * @param document
     * @param mapperElement
     */
    private void build(JPartTree partTree, JConfiguration configuration, Class<?> mapperClass, Method method,
            Document document, Element mapperElement) {

        JEntity entity = resolveEntity(configuration, mapperClass, method);
        Element parentElement = null;
        if (partTree.isDelete()) {
            parentElement = JElementBuilder.buildDeleteElement(document, method);
            JProviderHelper.deleteFromTable(configuration, document, parentElement, entity, null);
        } else if (partTree.isSelect()) {
            parentElement = JElementBuilder.buildSelectElement(document, method, null, null);
            Class<?> returnClass = getReturnClass(method.getGenericReturnType());
            if (entity.getEntityClass().equals(method.getReturnType()) || entity.getEntityClass().equals(returnClass)) {
                String resultMap = JProviderHelper.checkAndCreateResultMap(configuration, document, mapperElement,
                        entity);
                parentElement.setAttribute("resultMap", resultMap);
            } else {
                parentElement.setAttribute("resultType", returnClass.getCanonicalName());
            }
            JElementBuilder.appendChild(document, parentElement, "SELECT ");
            if (partTree.isDistinct()) {
                JElementBuilder.appendChild(document, parentElement, "DISTINCT ");
            }
            JElementBuilder.appendChild(document, parentElement, this.selectProperties(partTree, entity, method));
            JProviderHelper.fromTable(configuration, document, parentElement, entity, null);
        }
        where(partTree, entity, method, document, parentElement);
        orderBy(partTree, entity, method, document, parentElement);
        mapperElement.appendChild(parentElement);
    }

    /**
     * 解析实体
     * 
     * @param configuration
     * @param mapperClass
     * @param method
     * @return
     */
    private JEntity resolveEntity(JConfiguration configuration, Class<?> mapperClass, Method method) {
        return JProviderHelper.resolveEntity(configuration, mapperClass, JMethodMapper.class);
    }

    /**
     * 需要查询的列
     * 
     * @param partTree
     * @param entity
     * @param method
     */
    private String selectProperties(JPartTree partTree, JEntity entity, Method method) {
        List<JEntityColumn> columns = null;
        // 存在注解，优先处理
        if (method.isAnnotationPresent(JProperty.class)) {
            JProperty property = method.getAnnotation(JProperty.class);
            columns = selectProperties(entity, property);
        } else {
            columns = selectProperties(entity, partTree);
        }
        return selectProperties(entity, columns);
    }

    /**
     * 需要查询的列
     * 
     * @param entity
     * @param property
     */
    private List<JEntityColumn> selectProperties(JEntity entity, JProperty property) {
        List<JEntityColumn> columns = null;
        if (property.value().length > 0) {
            columns = new ArrayList<>(property.value().length);
            for (String prop : property.value()) {
                JEntityColumn column = entity.getColumn(prop);
                if (column == null) {
                    throw new JDBFlyException("类{} 中属性 {}无效", entity.getEntityClass().getCanonicalName(), prop);
                }
                columns.add(column);
            }
        } else if (property.exclude().length > 0) {
            Set<String> excludes = new HashSet<String>(Arrays.asList(property.exclude()));
            columns = entity.getColumns().stream()
                    .filter(item -> !excludes.contains(item.getProperty()) && item.isSelectable())
                    .collect(Collectors.toList());
        } else {
            columns = entity.getColumns().stream().filter(JEntityColumn::isSelectable).collect(Collectors.toList());
        }
        if (columns == null || columns.isEmpty()) {
            throw new JDBFlyException("@JProperty注解无效，无可用查询属性");
        }
        return columns;
    }

    /**
     * 需要查询的列
     * 
     * @param entity
     * @param partTree
     */
    private List<JEntityColumn> selectProperties(JEntity entity, JPartTree partTree) {
        List<JEntityColumn> columns = null;
        if (partTree.isExclude()) {
            Set<String> excludes = new HashSet<String>(partTree.getProperties());
            columns = entity.getColumns().stream()
                    .filter(item -> !excludes.contains(item.getProperty()) && item.isSelectable())
                    .collect(Collectors.toList());
        } else if (partTree.getProperties().size() > 0) {
            columns = new ArrayList<>(partTree.getProperties().size());
            for (String prop : partTree.getProperties()) {
                JEntityColumn column = entity.getColumn(prop);
                if (column == null) {
                    throw new JDBFlyException("类{} 中属性 {}无效", entity.getEntityClass().getCanonicalName(), prop);
                }
                columns.add(column);
            }
        } else {
            columns = entity.getColumns().stream().filter(JEntityColumn::isSelectable).collect(Collectors.toList());
        }
        if (columns == null || columns.isEmpty()) {
            throw new JDBFlyException("方法名无效，无可用查询属性");
        }
        return columns;
    }

    /**
     * 需要查询的列
     * 
     * @param entity
     * @param columns
     * @return
     */
    private String selectProperties(JEntity entity, List<JEntityColumn> columns) {
        StringBuilder sql = new StringBuilder();
        sql.append(columns.stream().map(JEntityColumn::getColumn).collect(Collectors.joining(", ")));
        sql.append(" ");
        return sql.toString();
    }

    /**
     * 处理Where条件部分XML
     * 
     * @param partTree
     * @param entity
     * @param method
     * @param document
     * @param parentElement
     */
    private void where(JPartTree partTree, JEntity entity, Method method, Document document, Element parentElement) {
        Parameter[] parameters = method.getParameters();
        JIntRef argCount = new JIntRef(0);
        Element whereElement = JElementBuilder.buildWhereElement(document);
        List<Node> items = null;
        if (method.isAnnotationPresent(JWhere.class)) {
            items = parseCriterias(entity, parameters, method.getAnnotation(JWhere.class).value(), argCount, document);
        } else {
            items = orWhere(entity, parameters, partTree.iterator(), argCount, document);
        }
        if (argCount.getValue() > parameters.length) {
            throw new JDBFlyException("实际所需参数数量大于方法参数数量");
        }
        if (items != null && !items.isEmpty()) {
            for (Node node : items) {
                whereElement.appendChild(node);
            }
            parentElement.appendChild(whereElement);
        }
    }

    private List<Node> parseCriterias(JEntity entity, Parameter[] parameters, JCriteria[] criterias, JIntRef argCount,
            Document document) {
        List<Node> items = new ArrayList<>();
        boolean first = true;
        for (JCriteria criteria : criterias) {
            List<Node> its = parseCriterions(entity, parameters, criteria.value(), argCount, document);
            if (its.isEmpty()) {
                continue;
            }
            if (first) {
                first = false;
                items.add(document.createTextNode("("));
            } else {
                items.add(document.createTextNode(JStringUtils.format(" {} (", criteria.isOr() ? "OR" : "AND")));
            }
            for (Node node : its) {
                items.add(node);
            }
            items.add(document.createTextNode(")"));
        }
        return items;
    }

    private List<Node> parseCriterions(JEntity entity, Parameter[] parameters, JCriterion[] criterions,
            JIntRef argCount, Document document) {
        boolean first = true;
        List<Node> items = new ArrayList<>();
        for (JCriterion criterion : criterions) {
            JEntityColumn column = entity.getColumn(criterion.value());
            if (column == null) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                items.add(document.createTextNode(JStringUtils.format(" {} ", criterion.isOr() ? "OR" : "AND")));
            }
            Object nodes = criterion.type().build(parameters, argCount.getValue(), column, document);
            extractNode(items, nodes, document);
            argCount.setValue(argCount.getValue() + criterion.type().getNumberOfArguments());
        }
        return items;
    }

    private List<Node> orWhere(JEntity entity, Parameter[] parameters, Iterator<JOrPart> iterator, JIntRef argCount,
            Document document) {
        List<Node> items = new ArrayList<>();
        boolean first = true;
        while (iterator.hasNext()) {
            JOrPart orPart = iterator.next();
            List<Node> its = andWhere(entity, parameters, orPart.iterator(), argCount, document);
            if (its.isEmpty()) {
                continue;
            }
            if (first) {
                first = false;
                items.add(document.createTextNode("("));
            } else {
                items.add(document.createTextNode(" OR ("));
            }
            for (Node node : its) {
                items.add(node);
            }
            items.add(document.createTextNode(")"));
        }
        return items;
    }

    private List<Node> andWhere(JEntity entity, Parameter[] parameters, Iterator<JPart> iterator, JIntRef argCount,
            Document document) {
        boolean first = true;
        List<Node> items = new ArrayList<>();
        while (iterator.hasNext()) {
            JPart part = iterator.next();
            JEntityColumn column = entity.getColumn(part.getProperty());
            if (column == null) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                items.add(document.createTextNode(" AND "));
            }
            Object nodes = part.getType().build(parameters, argCount.getValue(), column, document);
            extractNode(items, nodes, document);
            argCount.setValue(argCount.getValue() + part.getNumberOfArguments());
        }
        return items;
    }

    /**
     * 拼接Order子语句
     * 
     * @param partTree
     * @param entity
     * @param method
     */
    private void orderBy(JPartTree partTree, JEntity entity, Method method, Document document, Element parentElement) {
        if (partTree.isSelect()) {
            if (Collection.class.isAssignableFrom(method.getReturnType())) {
                CharSequence orderByClause = null;
                if (method.isAnnotationPresent(JOrderBy.class)) {
                    orderByClause = orderBy(entity, method.getAnnotation(JOrderBy.class));
                } else {
                    orderByClause = orderBy(entity, partTree.getSort());
                }
                if (JStringUtils.isEmpty(orderByClause)) {
                    orderByClause = entity.getOrderByClause();
                }
                if (JStringUtils.isNotEmpty(orderByClause)) {
                    JElementBuilder.appendChild(document, parentElement,
                            JStringUtils.format(" ORDER BY {}", orderByClause));
                }
            }
        }
    }

    private CharSequence orderBy(JEntity entity, JOrderBy orderBy) {
        StringBuilder sql = new StringBuilder();
        boolean first = true;
        for (JOrderProperty orderProperty : orderBy.value()) {
            JEntityColumn column = entity.getColumn(orderProperty.value());
            if (column == null) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                sql.append(", ");
            }
            sql.append(column.getColumn()).append(" ").append(orderProperty.strategy());
        }
        return sql;
    }

    private CharSequence orderBy(JEntity entity, JSort sort) {
        StringBuilder sql = new StringBuilder();
        if (sort.isSorted()) {
            Iterator<JOrder> iterator = sort.iterator();
            boolean first = true;
            while (iterator.hasNext()) {
                JOrder order = iterator.next();
                JEntityColumn column = entity.getColumn(order.getProperty());
                if (column == null) {
                    continue;
                }
                if (first) {
                    first = false;
                } else {
                    sql.append(", ");
                }
                sql.append(column.getColumn()).append(" ").append(order.getStrategy());
            }
        }
        return sql;
    }

    /**
     * 抽取节点属性
     * 
     * @param target
     * @param resource
     * @param document
     */
    private void extractNode(List<Node> target, Object resource, Document document) {
        if (resource == null) {
            return;
        }
        if (resource instanceof CharSequence) {
            target.add(document.createTextNode(resource.toString()));
        } else if (resource instanceof Node) {
            target.add((Node) resource);
        } else if (resource instanceof Object[]) {
            for (Object item : (Object[]) resource) {
                extractNode(target, item, document);
            }
        }
    }

    /**
     * 获得方法泛型返回数据实体类
     * 
     * @param mapperClass
     * @return
     */
    private Class<?> getReturnClass(Type returnType) {
        if (returnType instanceof ParameterizedType) {
            ParameterizedType t = (ParameterizedType) returnType;
            return (Class<?>) (t.getActualTypeArguments().length == 1 ? t.getActualTypeArguments()[0] : null);
        } else if (returnType instanceof Class<?>) {
            return (Class<?>) returnType;
        }
        return null;
    }
}
